<?php
namespace Lavender\Dao;
use Lavender\Core;
use Lavender\Db\Exception;
use Lavender\Db\Expression;
use Lavender\Errno;

/**
 * distributed table access class
 *
 */
abstract class TableDistributed extends Table
{

	/**
	 * distributed database count
	 * @var int
	 */
	protected $database_num = 1;

	/**
	 * distributed table count
	 * @var int
	 */
	protected $table_num = 1;


	protected function init()
	{
        $conf = Core::get_config('db', $this->database);
        $this->database_num = isset($conf['database_num']) ? intval($conf['database_num']) : 1;
        $this->table_num = isset($conf['table_num']) ? intval($conf['table_num']) : 1;
	}

	public function get_database_num(){
		return $this->database_num;
	}

	public function get_table_num(){
		return $this->table_num;
	}


    /**
     * multipie get
     * @param $ids
     * @param array $fields
     * @param array $filter
     * @param null $order
     * @param null $offset
     * @param null $length
     * @return array
     * @throws Exception
     * @throws \Lavender\Exception
     */
	public function get($ids, $fields = array(), array $filter = array(), $order = null, $offset = null, $length = null,$group  = null,$having  = null)
	{
		if (empty($ids)) {
			throw new Exception('ids can not be empty', Errno::PARAM_INVALID);
		}
		if(!is_array($ids)){
			$ids = [$ids];
		}
		
		//get items from all tables
		$items = array();
		$id_groups = $this->group_keys($ids);
		foreach ($id_groups as $db_id => $ids_groupby_table) {
			$db = Core::get_database($this->driver, $this->database, $db_id);
			$filter_condition = '';
			if (!empty($filter)) {
				$filter_condition = $this->build_condition(null,$filter, $db);
			}

			foreach ($ids_groupby_table as $table => $_ids) {
				$condition = '';
				if (!empty($ids) ) {
					$ids = array_map('intval', $ids);
					$condition = "{$this->first_key} IN ('" . implode("','", $_ids) . "')";
				}
				if (!empty($filter_condition)) {
					$condition = $condition ? "{$condition} AND {$filter_condition}" : $filter_condition;
				}
				$items_in_table = $db->get($table, $condition, $fields, $order, $offset, $length, $group, $having);
				$items = array_merge($items, $items_in_table);
			}
		}
		return $items;
	}

    /**
     * 一次删除多个
     * @param $ids
     * @param array $filter
     * @return int
     * @throws Exception
     * @throws \Lavender\Exception
     */
	public function delete_all($ids,array $filter = array())
	{
		if (!empty($ids) && !is_array($ids) ) {
			throw new Exception('param error', Errno::PARAM_INVALID);
		}
		$id_groups = $this->group_keys($ids);
        $result = 0;
		foreach ($id_groups as $db_id => $ids_groupby_table) {
			$db = Core::get_database($this->driver, $this->database, $db_id);
            $condition = $this->build_condition($ids,$filter, $db);
            if(empty($condition))
            {
                throw new Exception('delete all condition can not be empty', Errno::PARAM_INVALID);
            }
			foreach ($ids_groupby_table as $table => $_ids) {
				$db->delete($table, $condition);
				$result += $db->get_affected_rows();
			}
		}
        if ($result){
            self::set_sync_data(get_called_class(),__FUNCTION__,func_get_args());
        }
        return $result;
	}

    /**
     * @param $ids
     * @param $record
     * @param array $filter
     * @return int
     * @throws Exception
     * @throws \Lavender\Db\Exception
     */
    public function update_all($ids,$record,array $filter = array())
    {
        if(empty($record))
        {
            return 0;
        }
        $id_groups = $this->group_keys($ids);
        $result = 0;
        foreach ($id_groups as $db_id => $ids_groupby_table) {
            $db = Core::get_database($this->driver, $this->database, $db_id);
            $condition = $this->build_condition($ids,$filter, $db);
            foreach ($ids_groupby_table as $table => $_ids) {
                $db->update($table, $record ,$condition);
                $result += $db->get_affected_rows();
            }
        }
        //数据同步
        self::set_sync_data(get_called_class(), __FUNCTION__, func_get_args());
        return $result;
    }


    /**
     * @param $id
     * @return \Lavender\Db\Mysql
     * @throws \Lavender\Exception
     */
	public function get_database_instance($id = 0)
	{
		$db_id = $this->get_database_id($id);
		return Core::get_database($this->driver, $this->database, $db_id);
	}

	protected function format_first_key_as_int($id){
		return intval($id);
	}

	public function get_database_id($id = 0)
	{
		return $this->database_num > 1 ? $this->format_first_key_as_int($id) % $this->database_num : null;
	}

	public function get_table_index($id = 0){
		return (intval($this->format_first_key_as_int($id) / $this->database_num) % $this->table_num);
	}

    /**
     * @param int $id
     * @return string
     */
    public function get_table_name($id = 0)
	{
		if ($this->table_num <= 1) {
			return $this->table;
		}
		$index = $this->get_table_index($id);
		return $this->table . $index;
	}

    protected function check_id($id)
    {
        if (empty($id)) {
            throw new Exception('id can not be empty', Errno::PARAM_INVALID);
        }
    }


	/**
	 * group keys by db & table for multipie get
	 *
	 * @param array $ids
	 *
	 * @return array
	 */
	protected function group_keys($ids)
	{
		$group = array();

		if($ids === null){
			return $group;
		}
		
		foreach ($ids as $id) {
			$db_idx = $this->get_database_id($id);
			$table = $this->get_table_name($id);

			if (!isset($group[$db_idx])) {
				$group[$db_idx] = array();
			}

			if (!isset($group[$db_idx][$table])) {
				$group[$db_idx][$table] = array();
			}

			$group[$db_idx][$table][] = $id;
		}
		
		return $group;
	}


    /**
     * 批量插入
     * @param array $items
     * @param int $piece 每段数量
     * @return int
     */
    public function bulk_insert(array $items,$piece = 300){
		$num = count($items);
        if($num < 1){
            return 0;
        }
        if(count($items[0]) < 0){
            return 0;
        }
		if(!isset($items[0][$this->first_key])){
			throw new Exception("bulk_insert TableDistributed but first_key ".$this->first_key." not set");
		}
        $index = 0;
		$pieceItem = [];
		foreach($items as $item){
			$database_num = $this->get_database_id($item[$this->first_key]);
			if(!isset($pieceItem[$database_num])){
				$pieceItem[$database_num] = [];
			}
			$pieceItem[$database_num][] = $item;
		}

		foreach($pieceItem as $db => $item){
			$sql = $this->get_bulk_insert_sql($item);
			$this->get_database_instance($db)->execute($sql);
		}
        return  $num;
    }


    /**
     * single record get
     * @param array $filter $filter filter condition
     * @param null $order
     * @return mixed
     * @throws Exception
     */
    public function get_one(array $filter,array $fields = array(), $order = null){
		$key = $this->first_key;
		if(!isset($filter[$key])){
			throw new Exception("get_one for TableDistributed must need `{$key}` in the filter !");
		}
		$ids = $filter[$key];
		unset($filter[$key]);
		$data = $this->get($ids,$fields,$filter,$order,0,1);
		if($data && count($data) > 0){
			return $data[0];
		}
		return null;		
    }
}

